open Ast
open Printf

module NameMap = Map.Make(struct
	type t = string
	let compare x y = Pervasives.compare x y
end)


type note = {
	mutable pitch : int;
	mutable intensity : int;
	mutable duration : float;
}

type chord = {
	mutable notelist : note list;
}

type staff = {
	mutable instrument : int;
	mutable chordlist : chord list;
}

type part = {
	mutable bpm : int;
	mutable beatsig : float;  
	mutable stafflist : staff list;
}

type mtype = Int of int | Float of float | Bool of bool | Note of note | Chord of chord | Staff of staff | Part of part   
  
exception ReturnException of mtype * mtype NameMap.t
(* check pitch between *)
let getType v = 
	match v with
		Int(v) -> "int"
		| Float(v) -> "float"
		| Bool(v) -> "bool"
		| Note(v) -> "note"
		| Chord(v) -> "chord"
		| Staff(v) -> "staff"
		| Part(v) -> "part"  
(* fix names *)
let getInt v = 
	match v with
		Int(v) -> v
		| _ -> 0
		
let getFloat v = 
	match v with
		Float(v) -> v
		| _ -> 0.0	
		
let getBool v = 
	match v with
		Bool(v) -> v
		| _ -> false

let getNote v = 
	match v with
		Note(v) -> v
		| _ -> {pitch=128; intensity=0; duration=0.0}

let getChord v = 
	match v with
		Chord(v) -> v
		| _ -> {notelist=[]}
		
let getStaff v = 
	match v with
		Staff(v) -> v
		| _ -> {instrument=0; chordlist=[]}

let getPart v = 
	match v with
		Part(v) -> v
		| _ -> {bpm=90; beatsig=0.25; stafflist=[]}
  
let initIdentifier t = 
  match t with
    "int" -> Int(0)
    | "float" -> Float(0.0)
    | "bool" -> Bool(false)
    | "note" -> Note({pitch=128; intensity=0; duration=0.0})
    | "chord" -> Chord({notelist=[]})
    | "staff" -> Staff({instrument=0; chordlist=[]})
    | "part" -> Part({bpm=90; beatsig=0.25; stafflist=[]})
    | _ -> Bool(false)
   
let import_decl =
"
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.LinkedList;
import java.util.List;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;
"

let class_start = 
"
public class Output
{
	Sequence sequence = null;
	List<Track> trackList;
	int tempo;
	double beatSig;
	int beatTicks;
	
	public Output(int bpm, double beatsig)
	{
		this.tempo = bpm;
		this.beatSig = beatsig;
		beatTicks = (int) (beatsig/(1.0/32.0));
		trackList = new LinkedList<Track>();
		
		try
		{
			sequence = new Sequence(Sequence.PPQ, beatTicks);
		}
		catch (InvalidMidiDataException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		
		makeTrack();

		try
		{
			MidiSystem.write(sequence, 1, new File(\"output.mid\"));
			System.out.println(\"file written\");
		}
		catch (IOException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
	}
	
	private byte[] getTempo(int bpm)
	{
		int ms = 60000000;
		Integer i = new Integer(ms/bpm);
		BigInteger bi = new BigInteger(i.toString());
		return bi.toByteArray();
	}
	
	private Track createTrack(int instrument)
	{
		Track t = sequence.createTrack();
		try
		{
			MetaMessage mt = new MetaMessage();
			byte[] bt = getTempo(tempo);
			mt.setMessage(0x51 ,bt, bt.length);
			MidiEvent me = new MidiEvent(mt, 0);
			t.add(me);
			
			ShortMessage sm = new ShortMessage();
			sm.setMessage(192, instrument, 0);
			me = new MidiEvent(sm, 0);
			t.add(me);
		} catch (Exception e)
		{
			e.printStackTrace();
		}
		return t;
	}
	
	private MidiEvent createNoteOnEvent(int pitch, long lTick, int intensity)
	{
		return createNoteEvent(ShortMessage.NOTE_ON, pitch, intensity, lTick);
	}

	private MidiEvent createNoteOffEvent(int pitch, long lTick)
	{
		return createNoteEvent(ShortMessage.NOTE_OFF, pitch, 0, lTick);
	}
	
	private MidiEvent createNoteEvent(int nCommand, int nKey, int intensity, long lTick)
	{
		ShortMessage message = new ShortMessage();
		try
		{
			message.setMessage(nCommand, 0,	// always on channel 1
							   nKey,
							   intensity);
		}
		catch (InvalidMidiDataException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		MidiEvent	event = new MidiEvent(message,
										  lTick);
		return event;
	}
	
	private long getDuration(double duration)
	{
		long d = Math.round((duration/beatSig) * beatTicks);
		return d;
	}
	
	private List<MidiEvent> addNote(int startTick, int pitch, long lTick, int intensity)
	{
		List<MidiEvent> list = new LinkedList<MidiEvent>();
		System.out.println(startTick);
		System.out.println(startTick + lTick);
		
		list.add(createNoteOnEvent( pitch, intensity, startTick));
		list.add(createNoteOffEvent(pitch, startTick + lTick));
		
		return list;
	}
"

let staff_start = 
"
	private void makeTrack()
	{
		int tick;
		Track t;
"

let staff_end = 
"
	}
"

let main_start = 
"
public static void main(String[] args)
	{
"

let main_end = 
"
	}
"

let class_end = 
"
}
"

(* Main entry point: run a program *)
let run (vars, funcs) =
	(* Put function declarations in a symbol table *)
	let func_decls = List.fold_left
		(fun funcs fdecl -> NameMap.add fdecl.fname fdecl funcs)
		NameMap.empty funcs
	in
	
	(* Invoke a function and return an updated global symbol table *)
	let rec call fdecl actuals globals =
	
	(* Evaluate an expression and return (value, updated environment) *)
	let rec eval env = function
		IntLiteral(i) -> Int i, env
		| FloatLiteral(i) -> Float i, env
		| BoolLiteral(i) -> Bool i, env    
		| PitchLiteral(i) -> (
    		if String.length i == 3 || String.length i == 2 then
              (let octave = 
                 int_of_string (String.sub i (String.length i-1) 1)
               in
              (let sfvalue = 
                	if String.length i == 3 then
                      (if String.sub (String.lowercase i) 1 1 = "s" then 1
                      else if String.sub (String.lowercase i) 1 1 = "f" then -1
                      else raise (Failure ("invalid pitch: " ^ i)))
                 else 0
               in
              (match String.sub (String.lowercase i) 0 1 with
              "a" -> Int ((9 + sfvalue) + (12 * (octave + 1)))
              | "b" -> Int ((11 + sfvalue) + (12 * (octave + 1)))
              | "c" -> Int ((0 + sfvalue) + (12 * (octave + 1)))
              | "d" -> Int ((2 + sfvalue) + (12 * (octave + 1)))
              | "e" -> Int ((4 + sfvalue) + (12 * (octave + 1)))
              | "f" -> Int ((5 + sfvalue) + (12 * (octave + 1)))
              | "g" -> Int ((7 + sfvalue) + (12 * (octave + 1)))
              | _ -> raise (Failure ("invalid pitch: " ^ i))
              )))
            else if (String.lowercase i) = "r" then Int 128
            else raise (Failure ("invalid pitch: " ^ i))), env    
        | NoExpr ->	Bool true, env (* must be nonzero for the for loop predicate *) (* is this correct *)
		| Id(var) ->
			let locals, globals = env in
			if NameMap.mem var locals then
				(NameMap.find var locals), env
			else if NameMap.mem var globals then
				(NameMap.find var globals), env
			else raise (Failure ("undeclared identifier: " ^ var))
		| MemberAccess(var, i) -> 
			let v, env = eval env (Id var) in
				let vType = getType v in
				(match vType with 
				  | "note" -> 
					(match i with
					  "pitch" -> Int (getNote v).pitch
					  | "intensity" -> Int (getNote v).intensity
					  | "duration" -> Float (getNote v).duration
					  | _ -> raise (Failure ("invalid property of note: " ^ i)))
				  | "staff" ->
					(match i with
						"instrument" -> Int (getStaff v).instrument       
					  | _ -> raise (Failure ("invalid property of staff: " ^ i)))
				  | "part" -> 
					(match i with
						"bpm" -> Int (getPart v).bpm
					  | "beatsig" -> Float (getPart v).beatsig
					  | _ -> raise (Failure ("invalid property of part: " ^ i)))                            
				  | _ -> raise (Failure ("cannot access " ^ var ^ "." ^ i))), env                
		| BinOp(e1, op, e2) -> 
			let v1, env = eval env e1 in
			let v2, env = eval env e2 in
    		let v1Type = getType v1 in
    		let v2Type = getType v2 in
    		if v1Type = v2Type then
    			(match op with
    				Add -> 
         				if v1Type = "int" then
         					Int (getInt v1 + getInt v2)
             			else if v1Type = "float" then
               				Float (getFloat v1 +. getFloat v2)
                		else raise (Failure ("incorrect type: " ^ v1Type ^ " + " ^ v2Type))
    				| Sub -> 
         				if v1Type = "int" then
         					Int (getInt v1 - getInt v2)
             			else if v1Type = "float" then
               				Float (getFloat v1 -. getFloat v2)
                		else raise (Failure ("incorrect type: " ^ v1Type ^ " - " ^ v2Type))     
    				| Mult ->
         				if v1Type = "int" then
         					Int (getInt v1 * getInt v2)
             			else if v1Type = "float" then
               				Float (getFloat v1 *. getFloat v2)
                		else raise (Failure ("incorrect type: " ^ v1Type ^ " * " ^ v2Type))     
    				| Div ->
         				if v1Type = "int" then
         					Int (getInt v1 / getInt v2)
             			else if v1Type = "float" then
               				Float (getFloat v1 /. getFloat v2)
                		else raise (Failure ("incorrect type: " ^ v1Type ^ " / " ^ v2Type))     
    				| Mod -> 
         				if v1Type = "int" then
         					Int (getInt v1 mod getInt v2)
                		else raise (Failure ("incorrect type: " ^ v1Type ^ " % " ^ v2Type))     
    				| And -> 
         				if v1Type = "bool" then
         					Bool (getBool v1 && getBool v2)
                		else raise (Failure ("incorrect type: " ^ v1Type ^ " && " ^ v2Type))       
    				| Or -> 
         				if v1Type = "bool" then
         					Bool (getBool v1 || getBool v2)
                		else raise (Failure ("incorrect type: " ^ v1Type ^ " || " ^ v2Type))             
      				| Eq -> 
             			if v1Type = "int" then
               				Bool (getInt v1 = getInt v2)    
                		else if v1Type = "float" then
         					Bool (getFloat v1 = getFloat v2)
                		else if v1Type = "bool" then
         					Bool (getBool v1 = getBool v2)    
                		else if v1Type = "note" then
         					Bool (getNote v1 = getNote v2)
                		else if v1Type = "chord" then
         					Bool (getChord v1 = getChord v2)
                		else if v1Type = "staff" then
         					Bool (getStaff v1 = getStaff v2)        
                		else if v1Type = "part" then
         					Bool (getPart v1 = getPart v2)                    
                		else raise (Failure ("incorrect type: " ^ v1Type ^ " == " ^ v2Type))          				
        			| NEq -> 
             			if v1Type = "int" then
               				Bool (getInt v1 != getInt v2)    
                		else if v1Type = "float" then
         					Bool (getFloat v1 != getFloat v2)
                		else if v1Type = "bool" then
         					Bool (getBool v1 != getBool v2)      
                		else if v1Type = "note" then
         					Bool (getNote v1 != getNote v2)
                		else if v1Type = "chord" then
         					Bool (getChord v1 != getChord v2)
                		else if v1Type = "staff" then
         					Bool (getStaff v1 != getStaff v2)        
                		else if v1Type = "part" then
         					Bool (getPart v1 != getPart v2)                       
                		else raise (Failure ("incorrect type: " ^ v1Type ^ " != " ^ v2Type))      
           			| Less ->
             			if v1Type = "int" then
               				Bool (getInt v1 < getInt v2)    
                		else if v1Type = "float" then
         					Bool (getFloat v1 < getFloat v2)    
                  		else raise (Failure ("cannot compare: " ^ v1Type ^ " < " ^ v2Type))                    
              		| LEq ->
             			if v1Type = "int" then
               				Bool (getInt v1 <= getInt v2)    
                		else if v1Type = "float" then
         					Bool (getFloat v1 <= getFloat v2)
                  		else raise (Failure ("cannot compare: " ^ v1Type ^ " <= " ^ v2Type))               
                	| Greater -> 
             			if v1Type = "int" then
               				Bool (getInt v1 > getInt v2)    
                		else if v1Type = "float" then
         					Bool (getFloat v1 > getFloat v2)
                  		else raise (Failure ("cannot compare: " ^ v1Type ^ " > " ^ v2Type))          
                 	| GEq -> 
             			if v1Type = "int" then
               				Bool (getInt v1 >= getInt v2)    
                		else if v1Type = "float" then
         					Bool (getFloat v1 >= getFloat v2)
                  		else raise (Failure ("cannot compare: " ^ v1Type ^ " >= " ^ v2Type))), env
                else raise (Failure ("type mismatch: " ^ v1Type ^ " and " ^ v2Type))
      	(* must test thoroughly! *)
      	| Assign(var, e) ->
			let var1, env = eval env var in
			let e1, (locals, globals) = eval env e in
         	let v1Info =   
            	match var with
               		Id (i) -> ("id", (i, ""))
               		| MemberAccess(i, j) -> ("member", (i, j))
                 | _ -> raise (Failure ("left side of assignment must be an identifier or member access")) in         
         	let v1IdType = fst v1Info in
            let v1Name = snd v1Info in
         	let v1Type = (* ("note", "locals") *)
				(if NameMap.mem (fst v1Name) locals then
					(getType (NameMap.find (fst v1Name) locals), "locals")
				else if NameMap.mem (fst v1Name) globals then
					(getType (NameMap.find (fst v1Name) globals), "globals")
				else raise (Failure ("undeclared identifier: " ^ fst v1Name)))     		         
          	in  
         	let v1RetType = getType var1 in 
         	let e1Type = getType e1 in     
         		if v1RetType = e1Type then
             		match v1RetType with
    				"int" ->
    					if v1IdType = "id" then
    						(if snd v1Type = "locals" then
    							e1, (NameMap.add (fst v1Name) e1 locals, globals)
    						else if snd v1Type = "globals" then
    							e1, (locals, NameMap.add (fst v1Name) e1 globals)
    						else raise (Failure ("fatal error")))
    					else if v1IdType = "member" then
    						if fst v1Type = "note" then
    							if snd v1Name = "pitch" then
    								if getInt e1 >= 0 && getInt e1 <= 128 then
    									if snd v1Type = "locals" then
    										e1, (((getNote (NameMap.find (fst v1Name) locals)).pitch <- getInt e1); (locals, globals))
    									else if snd v1Type = "globals" then
    										e1, (((getNote (NameMap.find (fst v1Name) globals)).pitch <- getInt e1); (locals, globals))			
    									else raise (Failure ("fatal error"))
    								else raise (Failure ("invalid note pitch: " ^ string_of_int (getInt e1) ^ ". pitch must be between 0-128."))
    							else if snd v1Name = "intensity" then (* min max checking *)
    								if getInt e1 >= 0 && getInt e1 <= 127 then
    									if snd v1Type = "locals" then
    										e1, (((getNote (NameMap.find (fst v1Name) locals)).intensity <- getInt e1); (locals, globals))
    									else if snd v1Type = "globals" then
    										e1, (((getNote (NameMap.find (fst v1Name) globals)).intensity <- getInt e1); (locals, globals))			
    									else raise (Failure ("undeclared identifier: " ^ fst v1Name))
    								else raise (Failure ("invalid note intensity: " ^ string_of_int (getInt e1) ^ ". intensity must be between 0-127."))
    							else raise (Failure ("fatal error"))
    						else if fst v1Type = "staff" && (snd v1Name = "instrument") then
    							if snd v1Name = "instrument" then
    								if getInt e1 >= 0 && getInt e1 <= 127 then
    									if snd v1Type = "locals" then
    										e1, (((getStaff (NameMap.find (fst v1Name) locals)).instrument <- getInt e1); (locals, globals))
    									else if snd v1Type = "globals" then
    										e1, (((getStaff (NameMap.find (fst v1Name) globals)).instrument <- getInt e1); (locals, globals))			
    									else raise (Failure ("fatal error"))
    								else raise (Failure ("invalid staff instrument: " ^ string_of_int (getInt e1) ^ ". instrument must be between 0-127."))
    							else raise (Failure ("fatal error"))
    						else if fst v1Type = "part" then
    							if snd v1Name = "bpm" then
    								if getInt e1 >= 0 && getInt e1 <= 240 then
    									if snd v1Type = "locals" then
    										e1, (((getPart (NameMap.find (fst v1Name) locals)).bpm <- getInt e1); (locals, globals))
    									else if snd v1Type = "globals" then
    										e1, (((getPart (NameMap.find (fst v1Name) globals)).bpm <- getInt e1); (locals, globals))			
    									else raise (Failure ("fatal error"))
    								else raise (Failure ("invalid part bpm: " ^ string_of_int (getInt e1) ^ ". BPM must be between 0-240."))
    							else raise (Failure ("fatal error"))
    						else raise (Failure ("cannot assign to: " ^ fst v1Type)) 
    					else raise (Failure ("cannot assign to: " ^ fst v1Type)) 
    				| "float" ->
    					if v1IdType = "id" then
    						(if snd v1Type = "locals" then
    							e1, (NameMap.add (fst v1Name) e1 locals, globals)
    						else if snd v1Type = "globals" then
    							e1, (locals, NameMap.add (fst v1Name) e1 globals)
    						else raise (Failure ("fatal error")))
    					else if v1IdType = "member" then
    						if fst v1Type = "note" then
    							if snd v1Name = "duration" then
    								if getFloat e1 >= 0.0 && getFloat e1 <= 1.0 then
    									if snd v1Type = "locals" then
    										e1, (((getNote (NameMap.find (fst v1Name) locals)).duration <- getFloat e1); (locals, globals))
    									else if snd v1Type = "globals" then
    										e1, (((getNote (NameMap.find (fst v1Name) globals)).duration <- getFloat e1); (locals, globals))			
    									else raise (Failure ("fatal error"))
    								else raise (Failure ("invalid note duration: " ^ string_of_float (getFloat e1) ^ ". duration must be between 0.0-1.0."))
    							else raise (Failure ("fatal error"))
    						else if fst v1Type = "part" then
    							if snd v1Name = "beatsig" then
    								if getFloat e1 = 1.0 || getFloat e1 = 0.5 || getFloat e1 = 0.25 || getFloat e1 = 0.125 || getFloat e1 = 0.0625 || getFloat e1 = 0.03125 then
    									if snd v1Type = "locals" then
    										e1, (((getPart (NameMap.find (fst v1Name) locals)).beatsig <- getFloat e1); (locals, globals))
    									else if snd v1Type = "globals" then
    										e1, (((getPart (NameMap.find (fst v1Name) globals)).beatsig <- getFloat e1); (locals, globals))			
    									else raise (Failure ("fatal error"))
    								else raise (Failure ("invalid part beat signature: " ^ string_of_float (getFloat e1) ^ ". beat signature can only be: 1.0, 0.5, 0.25, 0.125, 0.0625, 0.03125."))
    							else raise (Failure ("fatal error"))
    						else raise (Failure ("cannot assign to: " ^ fst v1Type)) 
    					else raise (Failure ("cannot assign to: " ^ fst v1Type)) 
    				| _ -> (* bool, note, chord, staff, part *)
    					if v1IdType = "id" then
    						(if snd v1Type = "locals" then
    							e1, (NameMap.add (fst v1Name) e1 locals, globals)
    						else if snd v1Type = "globals" then
    							e1, (locals, NameMap.add (fst v1Name) e1 globals)
    						else raise (Failure ("fatal error")))
    					else raise (Failure ("cannot assign to: " ^ fst v1Type))              
           		else if v1IdType = "id" then
               		raise (Failure ("cannot assign: " ^ fst v1Type ^ " = " ^ e1Type))
             	else if v1IdType = "member" then
               		raise (Failure ("cannot assign: " ^ v1RetType ^ " = " ^ e1Type))
                else raise (Failure ("fatal error"))
        | Not(var) ->
      		let vnew, envnew = eval env var in 
          		if getType vnew = "bool" then
       				Bool(not (getBool vnew)), envnew  
            else raise (Failure ("type mismatch: !" ^ getType vnew))
        | Call("randint", [e]) -> 
			let v, env = eval env e in
          		if getType v = "int" then 
					Int(Random.int (getInt v)), env
            	else raise (Failure ("argument of randint must be an integer"))
		| Call("randfloat", [e]) ->
			let v, env = eval env e in
    			if getType v = "float" then 
					Float(Random.float (getFloat v)), env  
         		else raise (Failure ("argument of randfloat must be a float"))
		| Call("add", [var; e]) ->
    		let v1name = match var with
                       	Id(i) -> i
                		| _ ->  raise (Failure ("arguments of add must be identifiers")) in
    			ignore (match e with
                       	Id(i) -> "id"
                		| _ ->  raise (Failure ("arguments of add must be identifiers")));    
			let var1, env = eval env var in
			let e1, (locals, globals) = eval env e in
           	let v1Type = (* ("note", "locals") *)
  				(if NameMap.mem v1name locals then
  					(getType (NameMap.find v1name locals), "locals")
  				else if NameMap.mem v1name globals then
  					(getType (NameMap.find v1name globals), "globals")
  				else raise (Failure ("fatal error")))     		         
            in  
           	let v1RetType = getType var1 in 
           	let e1Type = getType e1 in  
				if v1RetType= "chord" && e1Type = "note" then
						(if snd v1Type = "locals" then
        					((getChord (NameMap.find v1name locals)).notelist <- getNote e1 :: (getChord (NameMap.find v1name locals)).notelist;
        					(NameMap.find v1name locals), (locals, globals)) 
						else if snd v1Type = "globals" then
        					((getChord (NameMap.find v1name globals)).notelist <- getNote e1 :: (getChord (NameMap.find v1name globals)).notelist;
        					(NameMap.find v1name globals), (locals, globals)) 
						else raise (Failure ("fatal error")))
				else if v1RetType= "staff" && e1Type = "note" then
						(if snd v1Type = "locals" then
        					((getStaff (NameMap.find v1name locals)).chordlist <- {notelist=[getNote e1]} :: (getStaff (NameMap.find v1name locals)).chordlist;
        					(NameMap.find v1name locals), (locals, globals))
						else if snd v1Type = "globals" then
        					((getStaff (NameMap.find v1name globals)).chordlist <- {notelist=[getNote e1]} :: (getStaff (NameMap.find v1name globals)).chordlist;
        					(NameMap.find v1name globals), (locals, globals))
						else raise (Failure ("fatal error"))) 
				else if v1RetType= "staff" && e1Type = "chord" then
						(if snd v1Type = "locals" then
        					((getStaff (NameMap.find v1name locals)).chordlist <- getChord e1 :: (getStaff (NameMap.find v1name locals)).chordlist;
        					(NameMap.find v1name locals), (locals, globals))
						else if snd v1Type = "globals" then
            				((getStaff (NameMap.find v1name globals)).chordlist <- getChord e1 :: (getStaff (NameMap.find v1name globals)).chordlist;	
            				(NameMap.find v1name globals), (locals, globals))
						else raise (Failure ("fatal error")))  
				else if v1RetType= "part" && e1Type = "staff" then
						(if snd v1Type = "locals" then
        					((getPart (NameMap.find v1name locals)).stafflist <- getStaff e1 :: (getPart (NameMap.find v1name locals)).stafflist;
        					(NameMap.find v1name locals), (locals, globals))
						else if snd v1Type = "globals" then
        					((getPart (NameMap.find v1name globals)).stafflist <- getStaff e1 :: (getPart (NameMap.find v1name globals)).stafflist;
        					(NameMap.find v1name globals), (locals, globals))
						else raise (Failure ("fatal error")))
				else raise (Failure ("cannot perform: add(" ^ fst v1Type ^ ", " ^  e1Type ^ ")")) 
        | Call("print", [e]) ->
      	let v, env = eval env e in
          (if getType v = "int" then
        	print_endline (string_of_int (getInt v))
          else if getType v = "float" then
            print_endline (string_of_float (getFloat v))
          else if getType v = "bool" then
            print_endline (string_of_bool (getBool v))
          else
            print_endline(getType v));
          (Bool false), env      
  		| Call("play", [e]) -> (* Writes the specified part to a java file to be written into midi *)
			ignore (match e with
                     	Id(i) -> i
              			| _ ->  raise (Failure ("argument of play must be an identifier")));   
      		let e1, env = eval env e in
      			(if getType e1 = "part" then
					let p = getPart(e1) in
					(let start = (import_decl ^ class_start ^ staff_start) in
					let oc = open_out "Output.java" in
						(fprintf oc "%s" start;
					let print_note x = 
						fprintf oc "%s\n" ("\nt.add(createNoteOnEvent(" ^ (string_of_int x.pitch) ^ 
							", tick," ^ (string_of_int x.intensity) ^ "));" ^
							"\ntick += getDuration(" ^ (string_of_float x.duration) ^ ");" ^
							"\nt.add(createNoteOffEvent(" ^ (string_of_int x.pitch) ^ ", tick));");
					in
					let print_chord x = 
						fprintf oc "%s\n" ("\nt.add(createNoteOnEvent(" ^ (string_of_int x.pitch) ^
							", tick," ^ (string_of_int x.intensity) ^ "));" ^
							"\nt.add(createNoteOffEvent(" ^ (string_of_int x.pitch) ^ ", tick + getDuration(" ^ (string_of_float x.duration) ^ ")));");
					in	
					let chord_iter y =
						(if List.length y.notelist < 2 then
							(List.iter print_note y.notelist)
						else
							(List.iter print_chord y.notelist;
							fprintf oc "%s\n" ("\ntick += getDuration(" ^ (string_of_float (List.hd y.notelist).duration) ^ ");");)
						)
						in
						let staff_iter staff = 
							(fprintf oc "%s\n" ("\nt = createTrack(" ^ (string_of_int staff.instrument) ^ ");" ^ "\ntick = 0;");
							List.iter chord_iter (List.rev staff.chordlist))
						in
							List.iter staff_iter (List.rev p.stafflist));
					fprintf oc "%s" "t.add(createNoteOnEvent(0, tick, 0)); \ntick += getDuration(0.5);\nt.add(createNoteOffEvent(0, tick));";
					fprintf oc "%s" staff_end;
					fprintf oc "%s" (
						main_start ^ "new Output(" ^ 
						(string_of_int (p.bpm)) ^ ", " ^ (string_of_float (p.beatsig)) ^
						");");
					fprintf oc "%s" main_end;
					fprintf oc "%s" class_end;
					close_out oc)
         		else raise (Failure ("argument of play must be a part")));
			(Bool false), env
		| Call(f, actuals) -> (* check *)
			let fdecl =
				try NameMap.find f func_decls
				with Not_found -> raise (Failure ("undefined function: " ^ f))
			in
			let actuals, env = List.fold_left
				(fun (actuals, env) actual ->
				let v, env = eval env actual in v :: actuals, env)
				([], env) actuals
			in
			let (locals, globals) = env in
			try
				let globals = call fdecl (List.rev actuals) globals
				in Bool false, (locals, globals)
			with ReturnException(v, globals) -> v, (locals, globals)
		in
		(* Execute a statement and return an updated environment *)
		let rec exec env = function
			Block(stmts) -> List.fold_left exec env stmts
			| Expr(e) -> let _, env = eval env e in env
			| If(e, s1, s2) ->
				let v, env = eval env e in
				exec env (if getBool v != false then s1 else s2)
			| While(e, s) ->
				let rec loop env =
					let v, env = eval env e in
					if getBool v != false then loop (exec env s) else env
				in loop env
			| For(e1, e2, e3, s) ->
				let _, env = eval env e1 in
				let rec loop env =
					let v, env = eval env e2 in
					if getBool v != false then
						let _, env = eval (exec env s) e3 in
						loop env
					else
						env
				in loop env
			| Return(e) ->
				let v, (locals, globals) = eval env e in
     			if getType v = fdecl.rettype then
					raise (ReturnException(v, globals))
        		else raise (Failure ("function " ^ fdecl.fname ^ " returns: " ^ getType v ^ " instead of " ^ fdecl.rettype))
		in (* done with below *)
		(* call: enter the function: bind actual values to formal args *)
		let locals =
			try List.fold_left2
				(fun locals formal actual ->
				NameMap.add formal.paramname actual locals)
			NameMap.empty fdecl.formals actuals
			with Invalid_argument(_) ->
				raise (Failure ("wrong number of arguments to: " ^ fdecl.fname))
		in
		let locals = List.fold_left (* Set local variables to 0 *)
			(fun locals local -> NameMap.add local.varname (initIdentifier local.vartype) locals)
			locals fdecl.locals
		in (* Execute each statement; return updated global symbol table *)
			snd (List.fold_left exec (locals, globals) fdecl.body)
		
		(* run: set global variables to 0; find and run "main" *)
		in let globals = List.fold_left
			(fun globals vdecl -> NameMap.add vdecl.varname (initIdentifier vdecl.vartype) globals) (* vdecl is the identifier, "X" *)
			NameMap.empty vars
		in try
			call (NameMap.find "main" func_decls) [] globals
		with Not_found ->
			raise (Failure ("did not find the main() function"))